/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package br.com.loja.model.service.callback;

import static br.com.loja.util.reflection.ReflectionUtil.getAllClassByAnnotation;
import static br.com.loja.util.reflection.ReflectionUtil.getBeanInstance;
import static br.com.loja.util.reflection.ReflectionUtil.getMethodByAnnotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;

/**
 *
 * @author DavidLuiz
 * @param <T> Object that will call the callback methods.
 */
public class CallerCallback<T> {

    private final Set<Object> callbacks = new HashSet<>();
    private final Class<T> clazz;

    public CallerCallback(Class<T> clazz) {
        this.clazz = clazz;
        setCallbacks();
    }

    private void setCallbacks() {
        for (Class<?> callbackClass : getAllClassByAnnotation(Callback.class)) {
            if (isWatchingThisEntity(getObservedEntity(callbackClass))) {
                addCallback(callbackClass);
            }
        }
    }

    private boolean isWatchingThisEntity(Class<?> classEntity) {
        return classEntity.isAssignableFrom(clazz);
    }

    private Class<?> getObservedEntity(Class<?> c) {
        Callback entityCallback = c.getAnnotation(Callback.class);
        Class<?> value = entityCallback.value();
        return value;
    }

    private void addCallback(Class<?> c) {
        callbacks.add(getBeanInstance(c.getName()));
    }

    public <C extends Annotation> void executeCallback(T entity, Class... annotationsClass) {
        for (Object callback : callbacks) {
            for (Method method : getCallbackMethods(annotationsClass, callback)) {
                invokeCallbackMethod(method, callback, entity);
            }
        }
    }

    private <C extends Annotation> Set<Method> getCallbackMethods(Class[] annotationsClass, Object callback) {
        Set<Method> methods = new HashSet<>();
        for (Class<C> annotation : annotationsClass) {
            for (Method m : getMethodByAnnotation(callback.getClass(), annotation)) {
                methods.add(m);
            }
        }
        return methods;
    }

    private void invokeCallbackMethod(Method method, Object callback, T entity) {
        try {
            method.invoke(callback, (T) entity);
        } catch (InvocationTargetException e) {
            System.err.println(e.getTargetException().getMessage());
            e.printStackTrace();
        } catch (IllegalAccessException | IllegalArgumentException ex) {
            System.err.println(ex.getMessage());
            ex.printStackTrace();
        }
    }

}
